/*-
 * VPEnhancedTime.java          TP-VP (Enhanced format)
 *
 * $LastChangedBy: stein $
 * $LastChangedRevision: 98 $
 * $LastChangedDate: 2007-02-20 15:18:26 +0000 (Tue, 20 Feb 2007) $
 * $HeadURL: svn://localhost/SMS/src/com/googlecode/attention/sms/pdu/VPEnhancedTime.java $
 *
 * Copyright (c) 2007, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.sms.pdu;

import static com.googlecode.attention.util.bit.ByteBitValues.*;

import com.googlecode.attention.util.Convert;
import com.googlecode.attention.util.IntArrayBuilder;

/**
 * TP-VP (Enhanced format).
 * <BR>
 * <BR>The TP-Validity Period comprises 7 octets. The presence of all
 * octets is mandatory although they may not all be used. The first
 * octet indicates the way in which the following 6 octets are used.
 * Any reserved/unused bits or octets must be set to zero.
 * <BR>
 * <BR>Octet 1 TP-VP functionality indicator
 * <BR>bit 7 Extension bit
 * <BR>Set to 1 if the TP-VP functionality indicator is to be extended
 * to another octet. A setting of 0 indicates that there are no more
 * TP-VP functionality indicator extension octets to follow. Any such
 * extension octet shall immediately follow the previous TP-VP
 * functionality indicator.
 * <BR>
 * <BR>bit 6 Single shot SM.
 * <BR>Set to 1 if the SC is required to make up to one delivery attempt.
 * The TP-Validity Period, where present, shall be applicable to the Single
 * shot SM.
 * <BR>
 * <BR>bits 5, 4, 3 Reserved
 * <BR>
 * <BR>bits 2, 1, 0 Validity Period Format.
 * <BR>Value bits 2 1 0
 * <BR>
 * <TABLE>
 * <TR><TD>0</TD><TD>0</TD><TD>0</TD><TD>No Validity Period specified.</TD></TR>
 * <TR><TD>0</TD><TD>0</TD><TD>1</TD><TD>Validity Period is as specified for the relative case. The following octet contains the TPVP value.</TD></TR>
 * <TR><TD>0</TD><TD>1</TD><TD>0</TD><TD>Validity period is relative in integer representation and the following octet contains the TP-VP value in the range 0 to 255 representing 0 to 255 seconds. A TP-VP value of zero is undefined and reserved for future use.</TD></TR>
 * <TR><TD>0</TD><TD>1</TD><TD>1</TD><TD>Validity period is relative in semi-octet representation. The following 3 octets contain the relative time in Hours, Minutes and Seconds giving the length of the validity period counted from when the SMS-SUBMIT is received by the SC. The representation of time uses the same representation as the Hours, Minutes and Seconds in the TP-Service-Centre-Time-Stamp.</TD></TR>
 * <TR><TD>1</TD><TD>0</TD><TD>0</TD><TD>Reserved</TD></TR>
 * <TR><TD>1</TD><TD>0</TD><TD>1</TD><TD>Reserved</TD></TR>
 * <TR><TD>1</TD><TD>1</TD><TD>0</TD><TD>Reserved</TD></TR>
 * <TR><TD>1</TD><TD>1</TD><TD>1</TD><TD>Reserved</TD></TR>
 * </TABLE>
 * <BR>
 * <BR>The SC shall reject any Unsupported/ Reserved values received by
 * returning the "TP-VP not supported" TP-FCS value in the Submit SM
 * Report for RP-Error.
 *
 * <BR><A HREF="http://www.dreamfabric.com/sms/vp.html">
 *              http://www.dreamfabric.com/sms/vp.html</A>
 *
 * @author Stein Gunnar Bakkeby
 */
public class VPEnhancedTime extends VP {
  
  /* Field values */
  private int bits7to0; // first octet
  private int[] data;
  private int extensionOctet; // Extends the functionality of the first oct.

  /* Static field values */
  /**
   * Internal validity period format for the enhanced time.
   */
  public enum FORMAT {
    /**
     * No Validity Period specified.
     */
    NO_VALIDITY_PERIOD_SPECIFIED(0),
    /**
     * Validity Period is as specified for the relative case.
     * The following octet contains the TPVP value.
     * 
     * @see VPRelativeTime
     */
    RELATIVE_TIME(1),
    /**
     * Validity period is relative in integer representation and the
     * following octet contains the TP-VP value in the range 0 to 255
     * representing 0 to 255 seconds (255 sec = 4 min 15 sec).
     * A TP-VP value of zero is undefined and reserved for future use.
     */
    VALIDITY_PERIOD_0_TO_255_SECONDS(2),
    /**
     * Validity period is relative in semi-octet representation. The
     * following 3 octets contain the relative time in Hours, Minutes
     * and Seconds giving the length of the validity period counted
     * from when the SMS-SUBMIT is received by the SC. The representation
     * of time uses the same representation as the Hours, Minutes and
     * Seconds in the TP-Service-Centre-Time-Stamp.
     */
    VALIDITY_PERIOD_HH_MM_SS(3),
    /** Reserved. */
    RESERVED_100(4),
    /** Reserved. */
    RESERVED_101(5),
    /** Reserved. */
    RESERVED_110(6),
    /** Reserved. */
    RESERVED_111(7);
    
    /**
     * The validity period format integer value.
     */
    public final int value;
    
    private FORMAT(final int value) {
      this.value = value;
    }
    
    /**
     * This will return the enum respective to the value.
     * 
     * @param value Integer value in the range 0 to 7
     * @return The enum, or throws an IllegalArgumentException
     * @throws IllegalArgumentException, if value &lt; 0 or &gt; 7
     */
    public static FORMAT getFormat(final int value) {
      if (value < 8 && value >= 0) {
        return FORMAT.values()[value];
      }
      throw new IllegalArgumentException(
          (value < 0 ? "Value < 0 (" : "Value > 7 (") + value + ")");
    }
  }
  
  /**
   * Default constructor, creates enhanced time with the value:
   * "00000000000000".
   */
  public VPEnhancedTime() {
    /* 7 octets (1 hdr + 6 data) and no extension */
    super(new int[7], VPF.ENHANCED_FORMAT);
  }
  
  /**
   * Constructor, takes octets representing the validity period as a time.
   * <BR>
   * <BR>The validity period format can be retrieved from the first octet
   * of the SMS-SUBMIT.
   *
   * @param octets The octet(s) representing validity period of an SMS-SUBMIT
   * @param vpf The Validity-Period-Format
   */
  public VPEnhancedTime(final int[] octets, final VPF vpf) {
    super(octets, vpf);
  }

  public int[] getVPOctets() {
    if (hasExtensionOctet()) {
      final IntArrayBuilder iab = new IntArrayBuilder(octets.length + 1);
      iab.append(octets);
      iab.append(extensionOctet);
      return iab.toArray();
    } // else
    return octets;
  }
  
  /**
   * This will return true if the enhanced time has an extension octet.
   * <BR>bit 7 Extension bit
   * <BR>Set to 1 if the TP-VP functionality indicator is to be extended
   * to another octet. A setting of 0 indicates that there are no more
   * TP-VP functionality indicator extension octets to follow. Any such
   * extension octet shall immediately follow the previous TP-VP
   * functionality indicator.
   * 
   * @return True if there is an extension octet, false otherwise
   */
  public boolean hasExtensionOctet() {
    return (octets[0] & B7) != 0; // 0x80
  }
  
  /**
   * This indicates whether the SM is a single shot or not.
   * <BR>
   * <BR>bit 6 Single shot SM.
   * <BR>Set to 1 if the SC is required to make up to one delivery attempt.
   * The TP-Validity Period, where present, shall be applicable to the Single
   * shot SM.
   * 
   * @return True if this is a single shot SM, false otherwise
   */
  public boolean isSingleShotSM() {
    return (octets[0] & B6) != 0; // 0x40
  }
  
  /**
   * This will return the VP format within the Enhanced Time format.
   * 
   * @return See static field values
   */
  public FORMAT getFormat() {
    return FORMAT.getFormat(octets[0] & RANGE_0_TO_2); // (B2|B1|B0)
  }
  
  /**
   * This will return a the VPRelativeTime data defined in this enhanced
   * time object, or throw an IllegalStateException if the data is not
   * of type Relative Time. Note that the object returned has been converted
   * from VPEnhancedTime into VPRelativeTime.
   * 
   * @return This object converted to VPRelativeTime,
   * or throws an IllegalStateException
   * @throws IllegalStateException If the VP format does not correspond
   * to Relative Time
   * @see FORMAT#RELATIVE_TIME
   */
  public VPRelativeTime getRelativeTime() throws IllegalStateException {
     switch (getFormat()) {
     case RELATIVE_TIME:
       return new VPRelativeTime(data[0], VPF.RELATIVE_FORMAT);
       
     default:
       throw new IllegalStateException(
           "The VP format does not correspond to Relative Time, see "
           + "the method getFormat() for more information.");
     }
  }
  
  /**
   * This will return the validity period in seconds.
   * 
   * This applies to the VP formats:
   * <BR> - VALIDITY_PERIOD_0_TO_255_SECONDS
   * <BR> - VALIDITY_PERIOD_HH_MM_SS
   * <BR>
   * <BR>The VALIDITY_PERIOD_0_TO_255_SECONDS will return the seconds
   * ranging from 0 to 255.
   * <BR>The VALIDITY_PERIOD_HH_MM_SS will return the seconds ranging
   * from 0 to 60.
   * <BR>Any other VP formats will result in an IllegalStateException
   * 
   * @return Validity period in seconds
   * @throws IllegalStateException If the VP format does not correspond
   * @see FORMAT#VALIDITY_PERIOD_0_TO_255_SECONDS
   * @see FORMAT#VALIDITY_PERIOD_HH_MM_SS
   */
  public int getSeconds() throws IllegalStateException {
    switch (getFormat()) {
    case VALIDITY_PERIOD_0_TO_255_SECONDS:
      return data[0];
   
    case VALIDITY_PERIOD_HH_MM_SS:
      /* Third octet */
      return data[2];
      
    default:
      throw new IllegalStateException(
          "The VP format does not correspond to Relative Seconds (0 - 255), "
          + "or VALIDITY_PERIOD_HH_MM_SS, see the method getFormat() "
          + "for more information.");
    }
  }
  
  /**
   * This will return the validity period in minutes.
   * 
   * This applies to the VP formats:
   * <BR> - VALIDITY_PERIOD_HH_MM_SS
   * <BR>
   * <BR>The VALIDITY_PERIOD_HH_MM_SS will return the minutes ranging
   * from 0 to 60.
   * <BR>Any other VP formats will result in an IllegalStateException
   * 
   * @return Validity period in minutes
   * @throws IllegalStateException If the VP format does not correspond
   * @see FORMAT#VALIDITY_PERIOD_HH_MM_SS
   */
  public int getMinutes() throws IllegalStateException {
    switch (getFormat()) {
    case VALIDITY_PERIOD_HH_MM_SS:
      /* Second octet */
      return data[1];
      
    default:
      throw new IllegalStateException(
          "The VP format does not correspond to VALIDITY_PERIOD_HH_MM_SS, "
          + "see the method getFormat() for more information.");
    }
  }
  
  /**
   * This will return the validity period in hours.
   * 
   * This applies to the VP formats:
   * <BR> - VALIDITY_PERIOD_HH_MM_SS
   * <BR>
   * <BR>The VALIDITY_PERIOD_HH_MM_SS will return the hours ranging
   * from 0 to 23.
   * <BR>Any other VP formats will result in an IllegalStateException
   * 
   * @return Validity period in hours
   * @throws IllegalStateException If the VP format does not correspond
   * @see FORMAT#VALIDITY_PERIOD_HH_MM_SS
   */
  public int getHours() throws IllegalStateException {
    switch (getFormat()) {
    case VALIDITY_PERIOD_HH_MM_SS:
      /* Second octet */
      return data[0];
      
    default:
      throw new IllegalStateException(
          "The VP format does not correspond to VALIDITY_PERIOD_HH_MM_SS, "
          + "see the method getFormat() for more information.");
    }
  }
  
  /* Set methods */
  /**
   * This will set the Enhanced Time format, or throw an exception if the
   * argument is not recognised. 
   * 
   * @param format See static field values
   */
  public void setFormat(final FORMAT format) {
    /* Bits 2 to 0 */
    switch(format) {
    case NO_VALIDITY_PERIOD_SPECIFIED:
    case RELATIVE_TIME:
    case VALIDITY_PERIOD_0_TO_255_SECONDS:
    case VALIDITY_PERIOD_HH_MM_SS:
    case RESERVED_100:
    case RESERVED_101:
    case RESERVED_110:
    case RESERVED_111:
      bits7to0 &= RANGE_3_TO_7;
      bits7to0 ^= (bits7to0 & RANGE_0_TO_2); // (B2|B1|B0)
      bits7to0 |= format.value;
      break;
      
    default:
      throw new IllegalArgumentException(
          "Use VPEnhancedTime static field values in setFormat(int)!");
    }
  }
  
  /**
   * This will set the Enhanced Time to indicate that the message is
   * a single shot SM.
   * <BR>
   * <BR>Set to true if the SC is required to make up to one delivery attempt.
   * The TP-Validity Period, where present, shall be applicable to the Single
   * shot SM.
   * 
   * @param value True if the message should be a single shot, false otherwise
   */
  public void setSingleShotSM(boolean value) {
    int res = bits7to0 & B6; // 0x40
    if (res != 0) bits7to0 ^= res;
    if (value) bits7to0 |= B6; // 0x40
  }
  
  /**
   * This will set the Enhanced Time to indicate that it has an
   * extension octet. Remember to set the extension octet using the
   * setExtensionOctet(String) method if this is set to true.
   * 
   * @param value True if this VP has an extension octet, false otherwise
   * @see #setExtensionOctet(int)
   */
  public void setHasExtensionOctet(boolean value) {
    int res = bits7to0 & B7; // 0x80
    if (res != 0) bits7to0 ^= res;
    if (value) bits7to0 |= B7; // 0x80
  }

  /**
   * This will set the extension octet that extends the functionality
   * of the VP's first octet. Note that this method does not automatically
   * indicate that the VP has an extension octet, do this using the
   * setHasExtensionBit(boolean).
   * 
   * @param extensionOctet The extension octet
   * @see #setHasExtensionBit(boolean)
   */
  public void setExtensionOctet(final int extensionOctet) {
    this.extensionOctet = extensionOctet;
    setHasExtensionOctet(true);
  }

  
  /**
   * This will set the Relative Time data. Note that this method will
   * throw an IllegalArgumentException if the data is not 1 octets long.
   * 
   * @param octet The relative time octet
   * @see #setRelativeTime(VPRelativeTime)
   */
  public void setVP(final int octet) {
    this.data = new int[] {octet};
  }
  
  /**
   * This will set the Relative Time data using a VPRelativeTime object.
   * 
   * @param rt The relative time object
   */
  public void setRelativeTime(final VPRelativeTime rt) {
    this.data = rt.getVPOctets();
  }
  
  /**
   * This will set the validity period in seconds.
   * 
   * This applies to the VP formats:
   * <BR> - VALIDITY_PERIOD_0_TO_255_SECONDS
   * <BR> - VALIDITY_PERIOD_HH_MM_SS
   * <BR>
   * <BR>The VALIDITY_PERIOD_0_TO_255_SECONDS can set the seconds to
   * range from 0 to 255.
   * <BR>The VALIDITY_PERIOD_HH_MM_SS can set the seconds to range
   * from 0 to 60.
   * <BR>Any other VP formats will result in an IllegalStateException
   * 
   * @param seconds The seconds to set
   * @throws IllegalStateException If the VP format does not correspond
   * @see FORMAT#VALIDITY_PERIOD_0_TO_255_SECONDS
   * @see FORMAT#VALIDITY_PERIOD_HH_MM_SS
   */
  public void setSeconds(final int seconds) {
    switch (getFormat()) {
    case VALIDITY_PERIOD_0_TO_255_SECONDS:
      data[0] = seconds;
      break;
   
    case VALIDITY_PERIOD_HH_MM_SS:
      /* Third octet */
      data[2] = seconds;
      break;
      
    default:
      throw new IllegalStateException(
          "The VP format does not correspond to Relative Seconds (0 - 255), "
          + "or VALIDITY_PERIOD_HH_MM_SS, see the method setFormat() "
          + "for more information.");
    }
  }
  
  /**
   * This will set the validity period in minutes.
   * 
   * This applies to the VP formats:
   * <BR> - VALIDITY_PERIOD_HH_MM_SS
   * <BR>
   * <BR>The VALIDITY_PERIOD_HH_MM_SS can set the minutes to range
   * from 0 to 60.
   * <BR>Any other VP formats will result in an IllegalStateException
   * 
   * @param minutes The minutes to set
   * @throws IllegalStateException If the VP format does not correspond
   * @see FORMAT#VALIDITY_PERIOD_HH_MM_SS
   */
  public void setMinutes(final int minutes) {
    switch (getFormat()) {
    case VALIDITY_PERIOD_HH_MM_SS:
      /* Second octet */
      data[1] = minutes;
      break;
      
    default:
      throw new IllegalStateException(
          "The VP format does not correspond to VALIDITY_PERIOD_HH_MM_SS, "
          + "see the method setFormat() for more information.");
    }
  }
  
  /**
   * This will set the validity period in hours.
   * 
   * This applies to the VP formats:
   * <BR> - VALIDITY_PERIOD_HH_MM_SS
   * <BR>
   * <BR>The VALIDITY_PERIOD_HH_MM_SS can set the hours to range
   * from 0 to 23.
   * <BR>Any other VP formats will result in an IllegalStateException
   * 
   * @param hours The minutes to set
   * @throws IllegalStateException If the VP format does not correspond
   * @see FORMAT#VALIDITY_PERIOD_HH_MM_SS
   */
  public void setHours(final int hours) {
    switch (getFormat()) {
    case VALIDITY_PERIOD_HH_MM_SS:
      /* First octet */
      data[0] = hours;
      break;
      
    default:
      throw new IllegalStateException(
          "The VP format does not correspond to VALIDITY_PERIOD_HH_MM_SS, "
          + "see the method setFormat() for more information.");
    }
  }
  
  /**
   * This will set the hours, minutes, and seconds based on the data
   * stored within a service-centre-time-stamp. Note that this method
   * only uses the SCTS's hour, minute, and second data, other data
   * such as day, month, year, and timezone are ignored.
   * 
   * @param scts The service-centre-time-stamp
   */
  public void setHHMMSS(final SCTS scts) {
    data[0] = scts.get(SCTS.HOUR);
    data[1] = scts.get(SCTS.MINUTE);
    data[2] = scts.get(SCTS.SECOND);
  }
  
  /**
   * @return &lt;relative time&gt; OR &lt;0-255 seconds&gt; OR &lt;HH:MM:SS&gt; OR ""
   */
  public String toString() {
    final String output;
    switch (getFormat()) {
    case RELATIVE_TIME:
      output = getRelativeTime().toString();
      break;
      
    case VALIDITY_PERIOD_0_TO_255_SECONDS:
      output = Integer.toString(getSeconds());
      break;
      
    case VALIDITY_PERIOD_HH_MM_SS:
      final char delimiter = ':';
      final StringBuilder sb = new StringBuilder();
      sb.append(Convert.intToString(getHours()));
      sb.append(delimiter);
      sb.append(Convert.intToString(getMinutes()));
      sb.append(delimiter);
      sb.append(Convert.intToString(getSeconds()));
      output = sb.toString();
      break;
      
    case RESERVED_100: /* falls through */
    case RESERVED_101: /* falls through */
    case RESERVED_110: /* falls through */
    case RESERVED_111: /* falls through */
    case NO_VALIDITY_PERIOD_SPECIFIED: /* falls through */
    default:
      output = "";
      break;
    }
    
    return output;
  }
}
